﻿using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace SCSInternetBanking.LibertyReserve
{
    public interface IApiAgent
    {
        string Host { get; set; }

        bool RequireValidSslCertificate { get; set; }

        int Timeout { get; set; }

        ApiCredentials Credentials { get; set; }

        Balance Balance();

        string AccountName(string accountNumber);

        HistoryResult History(HistorySpecification specification, int pageIndex, int pageSize);

        HistoryItem FindTransaction(long batchNumber);

        HistoryItem Transfer(Transfer transfer);
    }

    #region Models

    public  class ApiCredentials
    {
        public string AccountNumber { get; set; }

        public string ApiName { get; set; }

        public string SecurityWord { get; set; }

        public string CreateSecurityToken()
        {
            using (HashAlgorithm hasher = new SHA256Managed())
            {
                DateTime now = DateTime.UtcNow;
                string value = String.Format("{0}:{1:yyyyMMdd:HH}", SecurityWord, now);
                return BitConverter.ToString(hasher.ComputeHash(Encoding.UTF8.GetBytes(value))).Replace("-", string.Empty);
            }
        }
    }

    public enum Currency
    {
        Any = 0,

        Usd,

        Euro,

        Gold,
    }

    public enum SearchDirection
    {
        Any = 0,

        Incoming,

        Outgoing
    }

    public enum SearchPrivate
    {
        Any = 0,

        Yes,

        No
    }

    public enum Source
    {
        Any = 0,

        Site,

        Wallet,

        Api,

        Sci
    }

    public enum PaymentPurpose
    {
        None = 0,

        Service,

        Salary
    }

    public class Transfer
    {
        public string Reference { get; set; }

        public string Payee { get; set; }

        public Currency Currency { get; set; }

        public decimal Amount { get; set; }

        public string Memo { get; set; }

        public bool Private { get; set; }

        public PaymentPurpose Purpose { get; set; }
    }

    public class HistorySpecification
    {
        public DateTime From { get; set; }

        public DateTime Till { get; set; }

        public Currency Currency { get; set; }

        public SearchDirection Direction { get; set; }

        public string RelatedAccount { get; set; }

        public string Reference { get; set; }

        public Source Source { get; set; }

        public SearchPrivate Private { get; set; }

        public decimal AmountFrom { get; set; }

        public decimal AmountTo { get; set; }
    }

    public sealed class HistoryItem
    {
        public long Batch { get; set; }

        public DateTime Date { get; set; }

        public string Reference { get; set; }

        public string Payer { get; set; }

        public string PayerName { get; set; }

        public string Payee { get; set; }

        public string PayeeName { get; set; }

        public Currency Currency { get; set; }

        public decimal Amount { get; set; }

        public decimal Fee { get; set; }

        public decimal Balance { get; set; }

        public string Memo { get; set; }

        public bool Private { get; set; }

        public Source Source { get; set; }

        public override string ToString()
        {
            return string.Format("{0} {1}  {2} -> {3} {4} / {5} = {6} {7}",
                Batch.ToString().PadLeft(9),
                Date.ToString("HH:mm:ss"),
                Payer.PadLeft(8),
                Payee.PadRight(8),
                Amount.ToString().PadLeft(9),
                Fee.ToString().PadRight(6),
                Balance,
                Currency);
        }
    }

    public class HistoryResult
    {
        public HistoryItem[] Items { get; set; }

        public bool HasMore { get; set; }
    }

    public class Balance
    {
        public decimal Usd { get; set; }

        public decimal Euro { get; set; }

        public decimal Gold { get; set; }
    }

    public class ApiException : Exception
    {
        public ApiException()
        {
        }

        public ApiException(string message)
            : base(message)
        {
        }

        protected ApiException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        public ApiException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        public int ErrorCode { get; set; }
    }

    public enum ApiProtocol
    {
        None = 0,

        Xml,

        Nvp,

        Json,

        Soap
    }

    #endregion

    #region Helpers

    public static class ApiAgentFactory
    {
        public static IApiAgent Create(ApiProtocol protocol, ApiCredentials credentials)
        {
            IApiAgent agent = null;
            switch (protocol)
            {
                case ApiProtocol.Xml: agent = new XmlApiAgent(credentials);
                    break;
                case ApiProtocol.Nvp: agent = new NvpApiAgent(credentials);
                    break;
                case ApiProtocol.Json: agent = new JsonApiAgent(credentials);
                    break;
                case ApiProtocol.Soap: agent = new SoapApiAgent(credentials);
                    break;
                default:
                    throw new NotImplementedException();
            }

            return agent;
        }
    }

    public abstract class AbstractApiAgent : IApiAgent, IDisposable
    {
        private bool m_requireValidSslCertificate = true;

        protected AbstractApiAgent(ApiCredentials credentials)
        {
            Credentials = credentials;
            Host = "https://api2.libertyreserve.com";
            ContentType = "application/x-www-form-urlencoded";
            Timeout = 30000;
            Encoding = Encoding.UTF8;
        }

        public string ContentType { get; set; }

        public Encoding Encoding { get; set; }

        #region IApiAgent Members

        public string Host { get; set; }

        public bool RequireValidSslCertificate
        {
            get { return m_requireValidSslCertificate; }

            set
            {
                if (m_requireValidSslCertificate != value)
                {
                    if (value)
                    {
                        ServicePointManager.ServerCertificateValidationCallback -= new RemoteCertificateValidationCallback(SertificateValidation);
                    }
                    else
                    {
                        ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(SertificateValidation);
                    }

                    m_requireValidSslCertificate = value;
                }
            }
        }

        public ApiCredentials Credentials { get; set; }

        public int Timeout { get; set; }

        public abstract Balance Balance();

        public abstract string AccountName(string accountNumber);

        public abstract HistoryResult History(HistorySpecification specification, int pageIndex, int pageSize);

        public abstract HistoryItem FindTransaction(long batchNumber);

        public abstract HistoryItem Transfer(Transfer transfer);

        #endregion

        public void Dispose()
        {
            if (!m_requireValidSslCertificate)
            {
                ServicePointManager.ServerCertificateValidationCallback -= new RemoteCertificateValidationCallback(SertificateValidation);
            }
        }

        protected string SendRequest(string path, string body)
        {
            return Encoding.GetString(SendRequest(path, Encoding.GetBytes(body)));
        }

        protected byte[] SendRequest(string path, byte[] body)
        {
            var uriBuilder = new UriBuilder(Host)
            {
                Path = path
            };
            HttpWebRequest request = HttpWebRequest.Create(uriBuilder.Uri) as HttpWebRequest;
            request.Method = "POST";
            request.KeepAlive = false;
            request.ContentType = ContentType;
            request.UserAgent = "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3";
            request.Timeout = Timeout;
            request.ContentLength = body.Length;

            try
            {
                using (Stream requestStream = request.GetRequestStream())
                {
                    requestStream.Write(body, 0, body.Length);
                    requestStream.Flush();

                    using (WebResponse response = request.GetResponse())
                    {
                        using (MemoryStream ms = new MemoryStream())
                        {
                            byte[] buffer = new byte[1024];

                            using (Stream stream = response.GetResponseStream())
                            {
                                int count = 0;
                                while ((count = stream.Read(buffer, 0, buffer.Length)) > 0)
                                {
                                    ms.Write(buffer, 0, count);
                                }
                            }

                            return ms.ToArray();
                        }
                    }
                }
            }
            catch (WebException wex)
            {
                throw new ApiException("An error occured while connect to server", wex);
            }
        }

        private bool SertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            HttpWebRequest request = sender as HttpWebRequest;
            return sslPolicyErrors == SslPolicyErrors.None || (request != null && request.RequestUri.Host == new UriBuilder(Host).Host);
        }
    }

    #endregion
}
